import { initState } from "./initState";
import { compileToFunction } from "./compile/index";
import { callHook, mountComponent } from "./lifecycle";
import { generate } from "./compile/generate";
import { mergeOptions } from "./utils/index";

export function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    let vm = this;
    vm.$options = mergeOptions(Vue.options, options); // 将mixin里面的和配置项的进行合并
    callHook(vm, 'beforeCreate'); // 初始化状态之前，调用beforeCreate钩子
    // 初始化状态
    initState(vm);
    callHook(vm, 'created'); // 初始化状态之后，调用created钩子
    // 编译模板，没有el也可以走Vue实例的$mount方法
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  }
  Vue.prototype.$mount = function (el) {
    // el?render?render():template?template:outerHTML:$mount;
    /**
     * 在测试 el?render?render():template?template:outerHTML:$mount;的准确性时，
     * 如果是建一个vue2的vue-cli项目来试验，是行不通的，因为项目里面默认使用的是
     * vue2的runtime版本，其中不包含compiler编译模块，不会将template编译为vNode，
     * 需要使用完整版的vue.js，可以直接使用vue2版本的CDN资源建一个html文件去加载对
     * 应的vue文件来试验；
     */
    let vm = this;
    let options = vm.$options;
    el = document.querySelector(el); // 将id换为元素
    vm.$el = el;
    if (!options.render) {
      let template = options.template;
      if (!template && el) {
        // Compile el's outerHTML as template
        el = el.outerHTML; // el => <div id="app">hello</div>，dom的字符串
      } else {
        // Compile template into render function
        el = template;
      }
      let render = compileToFunction(el);
      options.render = render;
      // 挂载组件，Create vm.$el and replace "el" with it
      mountComponent(vm, el);
    } else {
      // 执行render函数
      options.render.bind(vm)((tag, param) => {
        let obj = { attrs: [], children: [], tag, type: 1 };
        if (typeof param === 'object') {
          function parseParam(ob) {
            obj.attrs = Object.keys(ob).map(key => {
              let o = {};
              o['name'] = key;
              o['value'] = ob[key];
              return o;
            })
          }
          parseParam(param);
        } else {
          obj.children = [{
            type: 3,
            text: param
          }]
        }
        let code = generate(obj);
        let render = new Function(`with(this) {return ${code}}`);
        options.render = render;
        mountComponent(vm, el);
      });
    }
  }
}